<!--
 * @Description: Stay hungry，Stay foolish
 * @Author: ''
 * @Date: 2023-10-22 14:59:50
 * @LastEditTime: 2023-10-22 16:40:46
 * @LastEditors: ''
-->
<template>
  <div ref="containerRef" :style="[style]">
    <slot />
  </div>
</template>

<script setup>
defineOptions({
  name: 'Watermark',
})
import {
  computed,
  onBeforeUnmount,
  onMounted,
  ref,
  shallowRef,
  watch,
} from 'vue'

import { useMutationObserver } from '@vueuse/core'
import { getPixelRatio, getStyleStr, reRendering } from './utils'
import useClips, { FontGap } from './useClip'
const props = defineProps({
  /**
   * @description The z-index of the appended watermark element
   */
  zIndex: {
    type: Number,
    default: 9,
  },
  /**
   * @description The rotation angle of the watermark
   */
  rotate: {
    type: Number,
    default: -22,
  },
  /**
   * @description The width of the watermark
   */
  width: Number,
  /**
   * @description The height of the watermark
   */
  height: Number,
  /**
   * @description Image source, it is recommended to export 2x or 3x image, high priority (support base64 format)
   */
  image: String,
  /**
   * @description Watermark text content
   */
  content: {
    type: [String, Array],
    default: '移动云',
  },
  /**
   * @description Text style
   */
  font: {
    // type: definePropType<WatermarkFontType>(Object),
    type: Object,
  },
  /**
   * @description The spacing between watermarks
   */
  gap: {
    type: Array,
    default: () => [100, 100],
  },
  /**
   * @description The offset of the watermark from the upper left corner of the container. The default is gap/2
   */
  offset: {
    type: Array,
  },
})

const style = {
  position: 'relative',
}

const color = computed(() => {
  if (props.font && props.font.color) {
    return props.font.color
  } else {
    return 'rgba(0,0,0,.15)'
  }
})

const fontSize = computed(() => {
  if (props.font && props.font.fontSize) {
    return props.font.fontSize
  } else {
    return 16
  }
})

const fontWeight = computed(() => {
  if (props.font && props.font.fontWeight) {
    return props.font.fontWeight
  } else {
    return 'normal'
  }
})

const fontStyle = computed(() => {
  if (props.font && props.font.fontStyle) {
    return props.font.fontStyle
  } else {
    return 'normal'
  }
})
const fontFamily = computed(() => {
  if (props.font && props.font.fontFamily) {
    return props.font.fontFamily
  } else {
    return 'sans-serif'
  }
})

const gapX = computed(() => props.gap[0])
const gapY = computed(() => props.gap[1])
const gapXCenter = computed(() => gapX.value / 2)
const gapYCenter = computed(() => gapY.value / 2)

const offsetLeft = computed(() => {
  if (props.offset) {
    return props.offset[0]
  } else {
    return gapXCenter.value
  }
})

const offsetTop = computed(() => {
  if (props.offset) {
    return props.offset[1]
  } else {
    return gapYCenter.value
  }
})

const getMarkStyle = () => {
  const markStyle = {
    zIndex: props.zIndex,
    position: 'absolute',
    left: 0,
    top: 0,
    width: '100%',
    height: '100%',
    pointerEvents: 'none',
    backgroundRepeat: 'repeat',
  }

  /** Calculate the style of the offset */
  let positionLeft = offsetLeft.value - gapXCenter.value
  let positionTop = offsetTop.value - gapYCenter.value
  if (positionLeft > 0) {
    markStyle.left = `${positionLeft}px`
    markStyle.width = `calc(100% - ${positionLeft}px)`
    positionLeft = 0
  }
  if (positionTop > 0) {
    markStyle.top = `${positionTop}px`
    markStyle.height = `calc(100% - ${positionTop}px)`
    positionTop = 0
  }
  markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`

  return markStyle
}

const containerRef = shallowRef(null)
const watermarkRef = shallowRef()
const stopObservation = ref(false)

const destroyWatermark = () => {
  if (watermarkRef.value) {
    watermarkRef.value.remove()
    watermarkRef.value = undefined
  }
}

const appendWatermark = (base64Url, markWidth) => {
  if (containerRef.value && watermarkRef.value) {
    stopObservation.value = true
    watermarkRef.value.setAttribute(
      'style',
      getStyleStr({
        ...getMarkStyle(),
        backgroundImage: `url('${base64Url}')`,
        backgroundSize: `${Math.floor(markWidth)}px`,
      })
    )
    if (containerRef.value) {
      containerRef.value.append(watermarkRef.value)
    }

    setTimeout(() => {
      stopObservation.value = false
    })
  }
}

/**
 * Get the width and height of the watermark. The default values are as follows
 * Image: [120, 64]; Content: It's calculated by content;
 */

const getMarkSize = (ctx) => {
  let defaultWidth = 120
  let defaultHeight = 64
  const image = props.image
  const content = props.content
  const width = props.width
  const height = props.height
  if (!image && ctx.measureText) {
    ctx.font = `${Number(fontSize.value)}px ${fontFamily.value}`
    const contents = Array.isArray(content) ? content : [content]
    const sizes = contents.map((item) => {
      const metrics = ctx.measureText(item)

      return [
        metrics.width,
        metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent,
      ]
    })
    defaultWidth = Math.ceil(Math.max(...sizes.map((size) => size[0])))
    defaultHeight =
      Math.ceil(Math.max(...sizes.map((size) => size[1]))) * contents.length +
      (contents.length - 1) * FontGap
  }

  const _width = width ? width : defaultWidth
  const _height = height ? height : defaultHeight
  return [_width, _height]
}

const getClips = useClips()

const renderWatermark = () => {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  const image = props.image
  const content = props.content
  const rotate = props.rotate
  if (ctx) {
    if (!watermarkRef.value) {
      watermarkRef.value = document.createElement('div')
    }
    const ratio = getPixelRatio()
    const [markWidth, markHeight] = getMarkSize(ctx)

    const drawCanvas = (drawContent) => {
      const [textClips, clipWidth] = getClips(
        drawContent || '',
        rotate,
        ratio,
        markWidth,
        markHeight,
        {
          color: color.value,
          fontSize: fontSize.value,
          fontStyle: fontStyle.value,
          fontWeight: fontWeight.value,
          fontFamily: fontFamily.value,
        },
        gapX.value,
        gapY.value
      )
      appendWatermark(textClips, clipWidth)
    }
    if(image){
      const img = new Image()
      img.onload = () => {
        drawCanvas(img)
      }
      img.onerror = () => {
        drawCanvas(content)
      }
      img.crossOrigin = 'anonymous'
      img.referrerPolicy = 'no-referrer'
      img.src = image
    }else{
      drawCanvas(content)
    }
  }
}

onMounted(()=>{
  renderWatermark()
})

watch(()=>props,()=>{
  renderWatermark()
},{
  deep:true,
  flush:'post'
})

onBeforeUnmount(()=>{
  destroyWatermark()
})

const onMutate = (mutations) => {
  if (stopObservation.value) {
    return
  }
  mutations.forEach((mutation) => {
    if (reRendering(mutation, watermarkRef.value)) {
      destroyWatermark()
      renderWatermark()
    }
  })
}

useMutationObserver(containerRef, onMutate, {
  attributes: true,
})
</script>
